package ids.dev.util;

import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NoResultException;
import javax.persistence.Persistence;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaQuery;

public class GenericDAO<T> implements Serializable {
	private static final long serialVersionUID = 1L;
	 
    private static final EntityManagerFactory emf = Persistence.createEntityManagerFactory("nossacontaPU");
    private EntityManager em;
 
    private Class<T> entityClass;
 
    public void beginTransaction() {
        em = emf.createEntityManager();
        em.getTransaction().begin();
    }
 
    public void commit() {
        em.getTransaction().commit();
    }
 
    public void rollback() {
        em.getTransaction().rollback();
    }
 
    public void closeTransaction() {
    	if(em.isOpen()){
    		em.close();
    	}
    }
 
    public void commitAndCloseTransaction() {
        commit();
        closeTransaction();
    }
 
    public void flush() {
        em.flush();
    }
 
    public void joinTransaction() {
        em = emf.createEntityManager();
        em.joinTransaction();
    }
 
    public GenericDAO(Class<T> entityClass) {
        this.entityClass = entityClass;
    }
 
    public void save(T entity) {
    	beginTransaction();
        em.persist(entity);
        commitAndCloseTransaction();
    }
 
    public void delete(Object id, Class<T> classe) {
    	beginTransaction();
        T entityToBeRemoved = em.getReference(classe, id);
        em.remove(entityToBeRemoved);
        commitAndCloseTransaction();
    }
    
    public void delete(List<Object> obj, Class<T> classe) {
    	beginTransaction();
    	for (Object object : obj) {
    		T entityToBeRemoved = em.getReference(classe, object);
            em.remove(entityToBeRemoved);
		}
        commitAndCloseTransaction();
    }
 
    public T update(T entity) {
    	beginTransaction();
    	T obj = em.merge(entity);
    	commitAndCloseTransaction();
        return obj;
    }
 
    public T find(int entityID) {
    	beginTransaction();
    	T value = em.find(entityClass, entityID);
    	closeTransaction();
        return value;
    }
 
    public T findReferenceOnly(int entityID) {
        return em.getReference(entityClass, entityID);
    }
 
    // Using the unchecked because JPA does not have a
    // em.getCriteriaBuilder().createQuery()<T> method
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public List<T> findAll() {
    	beginTransaction();
        CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
        cq.select(cq.from(entityClass));
        List list = em.createQuery(cq).getResultList();
        closeTransaction();
        return list;
    }
 
    // Using the unchecked because JPA does not have a
    // query.getSingleResult()<T> method
    @SuppressWarnings("unchecked")
    public T findOneResult(String namedQuery, Map<String, Object> parameters) {
        T result = null;
 
        try {
            Query query = em.createNamedQuery(namedQuery);
 
            // Method that will populate parameters if they are passed not null and empty
            if (parameters != null && !parameters.isEmpty()) {
                populateQueryParameters(query, parameters);
            }
 
            result = (T) query.getSingleResult();
 
        } catch (NoResultException e) {
            System.out.println("No result found for named query: " + namedQuery);
        } catch (Exception e) {
            System.out.println("Error while running query: " + e.getMessage());
            e.printStackTrace();
        }
 
        return result;
    }
 
    private void populateQueryParameters(Query query, Map<String, Object> parameters) {
        for (Entry<String, Object> entry : parameters.entrySet()) {
            query.setParameter(entry.getKey(), entry.getValue());
        }
    }
}
